// HSS.Forms.DataListColumn.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       DataListColumn.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/24/2010
// ----------------------------------------------------------------------------
namespace HSS.Forms
{
	#region Using Directives
	using System;
	using System.ComponentModel;
	using System.Reflection;
	using System.Windows.Forms;
	#endregion

	#region DataListColumn
	/// <summary>
	/// An DataListColumn knows which field of an object it should present.
	/// </summary>
	/// <remarks>
	/// The column knows how to:
	/// <list type="bullet">
	///	<item>extract its field from the row object</item>
	///	<item>convert a field to a string</item>
	///	<item>calculate the icon index for the row object</item>
	///	<item>extract a group "key" from the row object</item>
	///	<item>convert a group "key" into a Title for the group</item>
	/// </list>
	/// <para>For sorting to work correctly, fields from the same column
	/// must be of the same type, that is, the same field cannot sometimes
	/// return strings and other times integers.</para>
	/// </remarks>
	[Browsable(false)]
	public class DataListColumn : ColumnHeader
	{
		#region Constructor
		/// <summary>
		/// ctor
		/// </summary>
		public DataListColumn()
			: base()
		{
			this.ImageIndex = -1;
		}

		/// <summary>
		/// Initialize a column to have the given Title, and show the given field
		/// </summary>
		/// <param name="title">The Title of the column</param>
		/// <param name="field">The field to be shown in the column</param>
		public DataListColumn(string title, string field)
			: base()
		{
			this.Text = title;
			this.Name = title;
			this.FieldName = field;
			this.ImageIndex = -1;
		}

		/// <summary>
		/// Initialize a column to have the given Title, and show the given field
		/// </summary>
		/// <param name="title">The Title of the column</param>
		/// <param name="field">The field to be shown in the column</param>
		/// <param name="isTileViewColumn">Will this column be used when the view is switched to tile view</param>
		public DataListColumn(string title, string field, bool isTileViewColumn)
			: base()
		{
			this.Text = title;
			this.Name = title;
			this.FieldName = field;
			this.ImageIndex = -1;
			this.IsTileViewColumn = isTileViewColumn;
		}

		/// <summary>
		/// Initialize a column to have the given Title, and show the given field
		/// </summary>
		/// <param name="title">The Title of the column</param>
		/// <param name="field">The field to be shown in the column</param>
		/// <param name="valueGetter">This delegate will be used to extract a value to be displayed in this column</param>
		public DataListColumn(string title, string field, ValueGetterDelegate valueGetter)
			: base()
		{
			this.Text = title;
			this.Name = title;
			this.FieldName = field;
			this.ImageIndex = -1;
			this.ValueGetter = valueGetter;
		}

		/// <summary>
		/// Initialize a column to have the given Title, and show the given field
		/// </summary>
		/// <param name="title">The Title of the column</param>
		/// <param name="field">The field to be shown in the column</param>
		/// <param name="isTileViewColumn">Will this column be used when the view is switched to tile view</param>
		/// <param name="valueGetter">This delegate will be used to extract a value to be displayed in this column</param>
		public DataListColumn(string title, string field, bool isTileViewColumn, ValueGetterDelegate valueGetter)
			: base()
		{
			this.Text = title;
			this.Name = title;
			this.FieldName = field;
			this.ImageIndex = -1;
			this.IsTileViewColumn = isTileViewColumn;
			this.ValueGetter = valueGetter;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the text displayed in the column header
		/// </summary>
		/// <returns>The text displayed in the column header</returns>
		new public string Text
		{
			get { return base.Text; }
			set
			{
				base.Text = value;
				if (string.IsNullOrEmpty(this.Name))
					this.Name = value;
			}
		}

		/// <summary>
		/// The name of the property or method that should be called to get the value to display in this column.
		/// This is only used if a ValueGetterDelegate has not been given.
		/// </summary>
		[Category("Behavior")]
		[Description("The name of the property or method that should be called to get the field to display in this column")]
		public string FieldName
		{
			get { return fieldName; }
			set { fieldName = value; }
		} string fieldName;

		/// <summary>
		/// This format string will be used to convert a field to its string representation.
		/// </summary>
		/// <remarks>
		/// This string is passed as the first parameter to the String.Format() method.
		/// This is only used if ToStringDelegate has not been set.</remarks>
		/// <example>"{0:C}" to convert a number to currency</example>
		[Category("Behavior")]
		[Description("The format string that will be used to convert a value to its string representation")]
		[DefaultValue(null)]
		public string ValueToStringFormat
		{
			get { return valueToStringFormat; }
			set { valueToStringFormat = value; }
		} string valueToStringFormat;

		/// <summary>
		/// Gets or sets whether this column should be used when the view is switched to tile view.
		/// </summary>
		/// <remarks>Column 0 is always included in tileview regardless of this setting.
		/// Tile views do not work well with many "columns" of information, 2 or 3 works best.</remarks>
		[Category("Behavior")]
		[Description("Will this column be used when the view is switched to tile view")]
		[DefaultValue(false)]
		public bool IsTileViewColumn
		{
			get;
			set;
		}

		/// <summary>
		/// This delegate will be used to extract a value to be displayed in this column.
		/// </summary>
		/// <remarks>
		/// If this is set, FieldName is ignored.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ValueGetterDelegate ValueGetter
		{
			get { return valueGetter; }
			set { valueGetter = value; }
		} ValueGetterDelegate valueGetter;

		/// <summary>
		/// The delegate that will be used to translate the value to display in this column into a string.
		/// </summary>
		/// <remarks>If this value is set, ValueToStringFormat will be ignored.</remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ValueToStringConverterDelegate ValueToStringConverter
		{
			get { return valueToStringConverter; }
			set { valueToStringConverter = value; }
		} ValueToStringConverterDelegate valueToStringConverter;

		/// <summary>
		/// This delegate is called to get the image that should be shown in this column.
		/// It must return an index into the image list, or -1 for no image
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ImageGetterDelegate ImageGetter
		{
			get { return imageGetter; }
			set { imageGetter = value; }
		} ImageGetterDelegate imageGetter;

		/// <summary>
		/// This delegate is called to get the object that should be stored in the item tag.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TagGetterDelegate TagGetter
		{
			get { return tagGetter; }
			set { tagGetter = value; }
		} TagGetterDelegate tagGetter;

		/// <summary>
		/// This delegate is called to get the object that is the key for the group to which the given row belongs.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public GroupKeyGetterDelegate GroupKeyGetter
		{
			get { return groupKeyGetter; }
			set { groupKeyGetter = value; }
		} GroupKeyGetterDelegate groupKeyGetter;

		/// <summary>
		/// This delegate is called to convert a group key into a Title for that group.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter
		{
			get { return groupKeyToTitleConverter; }
			set { groupKeyToTitleConverter = value; }
		} GroupKeyToTitleConverterDelegate groupKeyToTitleConverter;

		/// <summary>
		/// Group objects by the initial letter of the field of the column
		/// </summary>
		/// <remarks>
		/// One common pattern is to group column by the initial letter of the value for that group.
		/// The field must be a string (obviously).
		/// </remarks>
		[Category("Behavior")]
		[Description("Group objects by the initial letter of the field of the column")]
		[DefaultValue(false)]
		public bool UseInitialLetterForGroup
		{
			get { return useInitialLetterForGroup; }
			set { useInitialLetterForGroup = value; }
		} bool useInitialLetterForGroup;

		/// <summary>
		/// The Previous Width of this column
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int PreviousWidth
		{
			get { return (_previousWidth); }
			set { _previousWidth = value; }
		} int _previousWidth = 0;

		/// <summary>
		/// The Largest Item Width of this column
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public float LargestWidth
		{
			get { return (_largestSize); }
			set { _largestSize = value; }
		} float _largestSize = -1.00F;


		#endregion

		#region Methods

		/// <summary>
		/// For a given row object, return the object that is to be displayed in this column.
		/// </summary>
		/// <param name="itemData">The row object that is being displayed</param>
		/// <returns>An object, which is the field to be displayed</returns>
		public object GetValue(HSS.Data.IDataContainer itemData)
		{
			if (this.valueGetter == null)
				return this.GetValueByName(itemData);
			else
				return this.valueGetter(itemData);
		}

		/// <summary>
		/// For a given row, extract the value using it's name.
		/// </summary>
		/// <param name="itemData">The data that is being displayed</param>
		/// <returns>An object, containing the valule found by FieldName</returns>
		protected object GetValueByName(HSS.Data.IDataContainer itemData)
		{
			if (string.IsNullOrEmpty(this.fieldName))
				return null;

			BindingFlags flags = BindingFlags.Public | BindingFlags.Instance |
				BindingFlags.InvokeMethod | BindingFlags.GetProperty | BindingFlags.GetField;
			try
			{
				if (itemData is Data.WrappedDataContainer)
					return ((Data.WrappedDataContainer)itemData).Data.GetType().InvokeMember(this.fieldName, flags, null, itemData, null);
				else
					return itemData.GetType().InvokeMember(this.fieldName, flags, null, itemData, null);
			}
			catch (System.MissingMethodException)
			{
				return String.Format("Missing method: {0}", this.fieldName);
			}
		}

		/// <summary>
		/// For a given row object, return the string representation of the value shown in this column.
		/// </summary>
		/// <remarks>
		/// For fields that are string (e.g. aPerson.Name), the field and its string representation are the same.
		/// For non-strings (e.g. aPerson.DateOfBirth), the string representation is very different.
		/// </remarks>
		/// <param name="itemData">An <see cref="HSS.Data.IDataContainer"/> object that contains the data for the current row</param>
		public string GetStringValue(HSS.Data.IDataContainer itemData)
		{
			return this.ValueToString(this.GetValue(itemData));
		}

		/// <summary>
		/// Convert the field object to its string representation.
		/// </summary>
		/// <remarks>
		/// If the column has been assigned a <see cref="ValueToStringConverterDelegate"/>, it will be used to do
		/// the conversion, else if you have specified the <c>ValueToStringFormat</c> property value
		/// it uses String.Format() with that format value; otherwise is just uses object.ToString(). A 
		/// null value object will always be converted to an empty strings.
		/// </remarks>
		/// <param name="value">The value of the field that should be displayed</param>
		/// <returns>A string representation of the field value</returns>
		public string ValueToString(object value)
		{

			string retVal = string.Empty;

			if (value == null)
				return retVal;

			if (this.valueToStringConverter != null)
			{
				retVal = this.valueToStringConverter(value);
				return retVal;
			}

			string fmt = this.ValueToStringFormat;
			if (String.IsNullOrEmpty(fmt))
				retVal = value.ToString();
			else
				retVal = String.Format(fmt, value);

			return retVal;
		}

		/// <summary>
		/// For a given row object, return the image that should displayed in this column.
		/// </summary>
		/// <param name="itemData">The row object that is being displayed</param>
		/// <returns>int (index into image list), -1 is no image</returns>
		public int GetImage(HSS.Data.IDataContainer itemData)
		{
			if (this.imageGetter == null)
				return -1;
			else
				return this.imageGetter(itemData);
		}

		/// <summary>
		/// For a given row object, return the object that should stored in the ListViewItem's tag.
		/// </summary>
		/// <param name="itemData">The row object that is being displayed</param>
		/// <returns>int (index into image list), -1 is no image</returns>
		public object GetTag(HSS.Data.IDataContainer itemData)
		{
			if (this.tagGetter == null)
				return null;
			else
				return this.tagGetter(itemData);
		}

		/// <summary>
		/// For a given row object, return the object that is the key of the group that this row belongs to.
		/// </summary>
		/// <param name="itemData">The row object that is being displayed</param>
		/// <returns>Group key object</returns>
		public object GetGroupKey(HSS.Data.IDataContainer itemData)
		{
			if (this.groupKeyGetter == null)
			{
				object key = this.GetValue(itemData);
				if (key is string && this.UseInitialLetterForGroup)
					key = ((string)key).Substring(0, 1);
				return key;
			}
			else
				return this.groupKeyGetter(itemData);
		}

		/// <summary>
		/// For a given group value, return the string that should be used as the groups Title.
		/// </summary>
		/// <param name="value">The row object that is being displayed</param>
		/// <returns>string</returns>
		public string ConvertGroupKeyToTitle(object value)
		{
			if (this.groupKeyToTitleConverter == null)
				return this.ValueToString(value);
			else
				return this.groupKeyToTitleConverter(value);
		}

		/// <summary>
		/// Create delegates used to group the columns using predefined grouping categories.
		/// </summary>
		/// <param name="GroupValues">Array of values. Values must be able to be compared to the item's value (using IComparable)</param>
		/// <param name="GroupDescriptions">The description for the matching values. The last element is the default description.
		/// If there are n values, there must be n+1 descriptions.</param>
		/// <example>
		/// this.column.GroupCategories(
		///     new int[] { 40000, 70000 },
		///     new string[] { "Entry Level",  "Management", "Executive"});
		/// </example>
		public void GroupCategories<T>(T[] GroupValues, string[] GroupDescriptions)
		{
			if (GroupValues.Length + 1 != GroupDescriptions.Length)
				throw new ArgumentException("GroupDescriptions must have one more element than values.");

			// Install a delegate that returns the index of the description to be shown
			this.GroupKeyGetter = delegate(HSS.Data.IDataContainer row)
			{
				Object itemValue = this.GetValue(row);
				if (itemValue == null || itemValue == System.DBNull.Value)
					return -1;
				IComparable comparable = (IComparable)itemValue;
				if (comparable == null)
					return -1;
				for (int i = 0; i < GroupValues.Length; i++)
				{
					if (comparable.CompareTo(GroupValues[i]) < 0)
						return i;
				}

				// Display the last element in the array
				return GroupDescriptions.Length - 1;
			};

			// Install a delegate that simply looks up the given index in the descriptions. 
			this.GroupKeyToTitleConverter = delegate(object key)
			{
				if ((int)key < 0)
					return "";

				return GroupDescriptions[(int)key];
			};
		}
		#endregion
	}

	#endregion
}